(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Portable Components, вспомогательные средства разработки ПО

Источник: habrahabr
nephrael


Продолжая свою предыдущую статью, посвященную библиотеке POCO (Portable Components), хотелось бы рассказать об оснастке POCO Application и её таких производных, как ServerApplication и ConsoleApplication. 
Оснастка Application создана для упрощения разработки ПО и, как правило, экономии времени. Пользуясь данной оснасткой, мы cможем создать консольные приложения, службы Windows и демоны UNIX за считанные минуты. 


Описание


Производные от Application делятся 2 группы: консольные и серверные. 
Оснастка включает в себя такие вещи, необходимые приложению, как:
  • Работа с аргументами командной строки на высоком уровне. Также имеется система проверки параметров на основе регулярных выражений и проверки на целочисленное значение.
  • Средства создания демонов UNIX и служб Windows.
  • Работа с загрузкой конфигурации. Этот пункт немаловажен в современном программном обеспечении. Конфигурацией можно задать любое поведение программы, не перекомпилируя проект полностью. Возможна загрузка из файлов или из реестра Windows.
  • Инициализация и завершение работы программы. Жизнь программы в POCO Application подчинена циклу: Инициализация - Выполнение прикладной задачи - Завершение работы. Такой порядок позволяет нам оформить прикладную часть в Main, а все второстепенные вещи спрятать подальше.
  • Средства логирования. Ни для кого не секрет, что грамотные системы сбора логов позволяют нам экономить время, а порой и деньги. POCO предоставляет нам очень мощные средства логирования. Логи можно отправлять в консоль, в файл, в журнал событий Windows, на сервер SYSLOG (например, когда узким местом системы является жёсткий диск). Также возможно комбинировать данные методы, задавать произвольный формат записи для каждого канала. В общем, очень мощный инструмент, с которым я вас обязательно познакомлю.
  • Создание подсистем приложения, оформление их в модуль и упаковка в динамическую библиотеку. Очень удобное средство для создания модульной системы, в которой модули можно заменять, не перекомпилируя программу.

Практика


Для создания программы с помощью данной оснастки необходимо наследоваться от Poco::Util::Application и перегрузить следующие методы:
  • void initialize(Application& self) //Инициализации приложения
  • void uninitialize() //Завершение работы приложения
  • void reinitialize(Application& self) //Перезапуск приложения
  • void defineOptions() //Объявление опций
  • void handleOption()	//Для замены обработчика комманд
  • int main(const std::vector<std::string>& args) //Точка входа для логики приложения

Параметры запуска приложения

Параметры запуска приложения в POCO реализуются с помощью класса Option. 
Каждый параметр имеет следующие свойства: 
  • Полное имя
  • Короткое имя
  • Символьное имя (1 символ)
  • Описание

Параметры могут быть сгруппированы и могут быть опциональными. На каждый параметр можно прикрепить валидаторы значения. В POCO предопределены два типа валидаторов: IntValidator - проверяет численные значения, RegExpValidator - проверяет параметр на соответствие с регулярному выражению. В случае, если программа запущена с непрошедшими валидацию параметрами, программа вернет ошибку и покажет все возможные опции, которые в свою очередь формируются автоматически. На параметры можно "вешать" функции-обработчики (callback'и), которые будут вызваны в случае использования этих параметров при инициализации. 

class myApp : public Application
{
public:
    myApp(int argc, char** argv) 
        : Application(argc,argv) 
        {}
    
    void initialize(Application& self)
    {
        cout << "Инициализация" << endl;
        loadConfiguration(); // Конфигурация по умолчанию
        Application::initialize(self);
    }
    void reinitialize()
    {
        cout << "Реинициализация" << endl;
        Application::uninitialize();
    }
    void uninitialize(Application& self)
    {
        cout << "Деинициализация" << endl;
        Application::reinitialize(self);
    }
    
    void HelpHim(const std::string& name, const std::string& value)
    {
        cout << "Здесь я чем-то должен им помочь" << endl;
    }
    
    void Configure(const std::string& name, const std::string& value)
    {
        cout << "Здесь я выдергиваю информацию из конфигурации" << endl;
    }
    
    void defineOptions(OptionSet& options)
    {
        cout << "Конфигурирование опций" << endl;
        Application::defineOptions(options);
        options.addOption(
            Option("help", "h", "Вывод доп. информации")
                .required(false)	//Обязательный параметр
                .repeatable(false)	//Возможно повторение
                //myApp::handleOption - функция-обработчик параметра
                .callback(OptionCallback<myApp>(this, &myApp::handleOption))); 
                
        options.addOption(
            Option("config-file", "f", "Загрузка конфигурации из файла")
                .required(false)
                .repeatable(true)
                .argument("file")
                .callback(OptionCallback<myApp>(this, &myApp::Configure)));

        options.addOption(
            Option("bind", "b", "Связать пару ключ=значение")
                .required(false)
                //Этот параметр - текстовое значение
                .argument("value")
                //Создаем валидатор, который проверяет, что значение целочисленное и лежит в [0; 100]
                .validator(new IntValidator(0, 100)) 
                .binding("test.property"));	//В случае использования данного параметра
    }
    
    int main(const std::vector<std::string>& args)
    {
        cout << "Запуск бизнес-логики" << endl;
    }
};

// Макрос POCO_APP_MAIN разворачивается во что-то вроде этого:
//   int wmain(int argc, wchar_t** argv) 
//   {
//       myApp A(argc,argv);
//       return A.run();
//   }
POCO_APP_MAIN(myApp)


Средства создания демонов UNIX и служб Windows.

Для создания сервера порой необходимо, чтобы её процесс был запущен от другого пользователя (например, от системы) и не занимал ресурсов у последнего. Также эта функция полезна для запуска приложения при старте ОС и не зависело от статуса пользователя. Реализация службы или демона в POCO сводится к наследованию от Poco::Util::ServerApplication. 

Реализуем класс некоторой задачи, которая будет являться логикой нашего сервера, например, каждую секунду будет писать в лог, сколько отработала наша программа:

class myServerTask: public Task
{
public:
    myServerTask(): Task("MyTask") //Регистрируем задачу под именем "MyTask"
    {
    }

    //Запуск задачи
    void runTask()
    {
        Application& app = Application::instance();
        while (!isCancelled())
        {
            //Ждем секунду
            sleep(1000);
            //Пишем в лог информацию
            Application::instance().logger().information
                                ("Приложение работает " + DateTimeFormatter::format(app.uptime()));
        }
    }
};

Далее реализуем непосредственно сервер:

class myServer: public ServerApplication
{
protected:
    void initialize(Application& self)
    {
        //Загружаем конфигурацию
        loadConfiguration();
        
        //Инициализируем ServerApplication
        ServerApplication::initialize(self);
        
        //Задаем логеру канал для вывода в файл
        logger().setChannel(AutoPtr<FileChannel>(new FileChannel("C:\\log.log")));
        
        //Выводим в лог строку
        logger().information("Инициализация");	
    }

    void uninitialize()
    {
        logger().information("Выключение");
        //Денициализируем ServerApplication
        ServerApplication::uninitialize();
    }

    int main(const std::vector<std::string>& args)
    {
        if (!config().getBool("application.runAsDaemon") && 
            !config().getBool("application.runAsService"))
        {
            //Выполняем действия для обработки запуска 
            //приложения как НЕ СЕРВИСА и НЕ ДЕМОНА
            cout << "Вы запустили приложения напрямую, запустите её как сервис или демон" << endl;
        }
        else
        {
            //А тут мы запустили как сервис или демон
            //можно работать
            
            //Создаем менеджер задач
            TaskManager tm;
            
            //Создаем и запускаем нашу задачу
            tm.start(new myServerTask);
            
            //Ждем сигнала о завершении работы
            waitForTerminationRequest();
            
            //Закругляем все задачи и потоки
            tm.cancelAll();
            tm.joinAll();
        }


        //Профит
        return Application::EXIT_OK;
    }
};

//Запускаем сервер
POCO_SERVER_MAIN(myServer)


Всё, сервис и демон написаны.
Теперь компилируем и регистрируем сервис Windows следующими ключами:
  • Для регистрации службы Windows: /registerService
  • Для выключения службы Windows: /unregisterService
  • Для смены имени службы Windows: /displayName "Name"


Запуск и завершение приложения осуществляется следующим образом:
  • Для запуска демона Unix: --daemon
  • Для запуска службы Windows выполняем в коммандной строке: net start <Приложение>
  • Для завершения демона killall <Приложение>
  • Для завершения сервиса net stop <Приложение>


Загрузка конфигурации

Конфигурация загружается методом:
void loadConfiguration(const std::string& path, int priority = PRIO_DEFAULT);

Тип файла определяется расширением:
  • .properties - Properties file (PropertyFileConfiguration)
  • .ini - Initialization file (IniFileConfiguration)
  • .xml - XML file (XMLConfiguration)

Как только данные загружены их можно использовать. В POCO модель данных представляет собой дерево, в котором доступ к каждому элементу задается строкой.
Например XML:
<?xml version="1.0" encoding="UTF-8"?>
<recipe name="хлеб" preptime="5" cooktime="180">
  <title>Простой хлеб</title>
  <composition>
    <ingredient amount="3" unit="стакан">Мука</ingredient>
    <ingredient amount="0.25" unit="грамм">Дрожжи</ingredient>
    <ingredient amount="1.5" unit="стакан">Тёплая вода</ingredient>
    <ingredient amount="1" unit="чайная ложка">Соль</ingredient>
  </composition>
  <instructions>
    <step>Смешать все ингредиенты и тщательно замесить.</step>
    <step>Закрыть тканью и оставить на один час в тёплом помещении.</step>
    <!-- <step>Почитать вчерашнюю газету.</step> - это сомнительный шаг... -->
    <step>Замесить ещё раз, положить на противень и поставить в духовку.</step>
  </instructions>
</recipe>

Грузим так:

void initialize(Application& self)
{
    ofstream file("out.txt");
    cout << "Инициализация" << endl;
    loadConfiguration("a:\\conf.xml");
    
    file << "Мы готовим: " << config().getString("title") << endl
         << "Для этого нам надо: "   << config().getString("composition.ingredient[0]") << " : "
                                    << config().getString("composition.ingredient[0][@amount]") << " " 
                                    << config().getString("composition.ingredient[0][@unit]") 
                                    << endl
                                    << config().getString("composition.ingredient[1]") << " : "
                                    << config().getString("composition.ingredient[1][@amount]") << " " 
                                    << config().getString("composition.ingredient[1][@unit]") 
                                    << endl
                                    << config().getString("composition.ingredient[2]") << " : "
                                    << config().getString("composition.ingredient[2][@amount]") << " " 
                                    << config().getString("composition.ingredient[2][@unit]") 
                                    << endl
                                    << config().getString("composition.ingredient[3]") << " : "
                                    << config().getString("composition.ingredient[3][@amount]") << " " 
                                    << config().getString("composition.ingredient[3][@unit]") 
                                    << endl
        << "Выполняем шаги: "       << endl
                                    << config().getString("instructions.step[0]") << endl
                                    << config().getString("instructions.step[1]") << endl
                                    << config().getString("instructions.step[2]") << endl;   

    int timeToCook = config().getInt("[@cooktime]");
    file << "Время на готовку: " << timeToCook << endl;
    
    file.close();
            
}

Результат такой:
Мы готовим: Простой хлеб
Для этого нам надо: Мука: 3 стакан
Дрожжи: 0.25 грамм
Тёплая вода: 1.5 стакан
Соль: 1 чайная ложка
Выполняем шаги: 
Смешать все ингредиенты и тщательно замесить.
Закрыть тканью и оставить на один час в тёплом помещении.
Замесить ещё раз, положить на противень и поставить в духовку.
Время на готовку: 180

Аналогичным образом можно парсить и INI. Соответственно здесь будет всегда идентификатор вида "категория.ключ".
Например
;INI-File
[Group]
ValueText = "hello world"
IntValue = 123

Грузим так:

std::string text = config().getString("Group.ValueText"); // text == "Hello world"
int value = config().getInt("Group.IntValue"); // value == 123

Файлы .property имеют имя самой переменной в файле
;Java property file
Value.Text = "hello world"
Int.Value = 123

Грузим так:

std::string text = config().getString("Value.Text"); // text == "Hello world"
int value = config().getInt("Int.Value"); // value == 123


Средства логирования

Средства логирования состоят из четырех основных частей:
  • Логер
  • Канал
  • Объект хранения данных (файл, база данных)
  • Форматер

Логер является в приведенной цепочке звеном, к которому обращается наше приложение для отправки данных в лог. Единицей процесса логирования является сообщение. 
Сообщение представляет из себя объект, имеющий: 
  • Источник данных (заранее выбранное текстовое значение)
  • Данные - строка, несущая в себе полезную информацию о событии
  • Временную метку
  • Приоритет сообщения
  • Идентификаторы процесса (PID) и потока (TID)
  • Некоторые опциональные параметры

Приоритеты выставлены в следующей последовательности (от низкого к высокому):
  • Трассировочная информация (Trace)
  • Отладочная информация (Debug)
  • Техническая информация (Information)
  • Напоминание (Notice)
  • Предупреждение (Warning)
  • Ошибка (Error)
  • Критическая ошибка (Critical)
  • Фатальная ошибка (Fatal)

Данные представлены строкой, однако в неё можно закодировать и другие данные. Временная метка создается с точностью до микросекунды.

Канал - связующее звено между логером и объектом хранения данных.
Существует несколько базовых каналов:
  • ConsoleChannel - как не сложно догадаться, это канал, который выводит данные в стандартный поток вывода STDOUT
  • WindowsConsoleChannel - специфичный для Windows консольный канал, который выводит данные в std::clog
  • NullChannel - отвергает все данные
  • SimpleFileChannel - простой канал для вывода в файл, причем каждое новое сообщение на новой строке. Имеет вшитый максимальный размер файла. Умеет использовать вторичный файл для хранения данных, когда первичный превышает максимальный размер.
  • FileChannel - полноприводный файловый канал. Поддерживает архивирование, часовые пояса, сжатие, максимальное время жизни лога.
  • EventLogChannel - специфичный для Windows канал данных, позволяющий выводить сообщения в системный журнал событий Windows.
  • SyslogChannel - канал, который отправляет сообщения на сервер демона syslog.
  • AsyncChannel - мост, позволяющий отправлять сообщения на любой канал асинхронно.
  • SplitterChannel - канал, позволяющий отправить одно сообщение на несколько каналов


Пример использования логера:

//Консольный канал
AutoPtr<ConsoleChannel> console(new ConsoleChannel);
//Задаем формат
AutoPtr<PatternFormatter> formater(new PatternFormatter);
formater->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
//Форматер канала
AutoPtr<FormattingChannel> formatingChannel(new FormattingChannel(formater, console));
//Создаем логер
Logger::root().setChannel(formatingChannel);

//Оправляем логеру сообщение
Logger::get("Console").information("Сообщение в консоль");

//Создаем форматированный канал записи в файл
AutoPtr<FormattingChannel> file(new FormattingChannel(formater, AutoPtr<FileChannel>(new FileChannel("A:\\123.txt"))));
//Создаем логер
Logger::create("File", file);
//Отправляем данные
Logger::get("File").fatal("I want to play a game. Это сообщение в файл");

//Создаем разветвляющий канал
AutoPtr<SplitterChannel> splitter(new SplitterChannel);
//Добавляем в него каналы консоли и файла
splitter->addChannel(file);
splitter->addChannel(console);
//Создаем для них логер
Logger::create("AllChannel", file);

//Пишем в логер сообщение
Logger::get("AllChannel").fatal("Сообщение в консоль и файл");

//Создаем канал системного журнала
AutoPtr<EventLogChannel> event(new EventLogChannel);
//Создаем логер
Logger::create("Event", event);
//Пишем сообщение в системный журнал (только для Windows)
Logger::get("Event").fatal("Сообщение в системный журнал");


Оформляем классы в отдельные модули

В POCO основная концепция - модульность любой ценой, а добиться такой модульности во время выполнения можно хорошим средством - загрузчиком классов (ClassLoader), позволяющим загрузку из динамических библиотек.
Реализуем абстрактный класс сортировки массива.
Для экспорта необходимо в базовом классе реализовать конструктор по умолчанию и виртуальный деструктор, а также создать чисто виртуальный метод virtual string name() const = 0; и в классе-наследнике реализовать его.

//Файл sort.h
class ABaseSort
{
protected:
    vector<int> array; //Массив для манипуляций
public:
    ABaseSort () {}	//конструктор по-умолчанию 
    virtual ~ABaseSort() {}	//деструктор
    virtual string name() const = 0; //специальный метод name , выводящий имя реализации
    
    //Собственно наш рабочий метод
    virtual void sort() = 0;

    //И методы ввода-вывода
    void loadVector(vector<int>& lArray)
    {
        array.assign(lArray.begin(), lArray.end());
    }

    vector<int> getArray()
    {
        return array;
    }

    //Xor-swap
    static void swap(int &A, int &B)
    {
        A ^= B ^= A ^= B;
    }
};

Далее создадим 2 класса сортировки: методом пузырька и стандартным методом STL (stable_sort)

//Класс сортировки методом пузырька
//Файл sort.cpp
#include "sort.h"
class bubbleSort : public ABaseSort
{
public:
    //Метод выводит имя
    string name() const
    {
        return "Bubble Sort";
    }

    //А здесь собственно логика сортировки
    void sort()
    {
        size_t size = array.size();
        for (int i=0; i<size-1; ++i)
            for (int j=i; j<size; ++j)
                if (array[i] > array[j])
                    swap(array[i],array[j]);
    }
};

//Класс сортировки методом STL (std::stable_sort)
class stableSort : public ABaseSort
{
public:
    //Метод выводит имя
    string name() const
    {
        return "Stable Sort";
    }

    //А здесь собственно логика сортировки
    void sort()
    {
        stable_sort(array.begin(), array.end());
    }
};

Осталось добавить параметры экспорта

POCO_BEGIN_MANIFEST(ABaseSort)	//Выгружаем базовый класс
    POCO_EXPORT_CLASS(bubbleSort) //Выгружаем класс сортировки методом пузырька
    POCO_EXPORT_CLASS(stableSort) //Выгружаем класс сортировки методом stable_sort
POCO_END_MANIFEST

Компилируем проект как динамическую библиотеку.
А теперь давайте воспользуемся нашими классами.

//Файл logic.cpp
#include "sort.h"
//Создаем загрузчик с базовым классом ABaseSort
Poco::ClassLoader<ABaseSort> loader;

loader.loadLibrary("myImportedFile.dll");	//Загружаем динамическую библиотеку
if (loader.isLibraryLoaded("myImportedFile.dll"))
{
    //Выведем все доступные классы
    cout << "Доступны следующие классы сортировки: " << endl;
    for (auto it = loader.begin(); it != loader.end(); ++it)
    {
        cout << "В библиотеке '" << it->first << "': " << endl;
        for (auto jt = it->second->begin(); jt != it->second->end(); ++jt)
        {
            cout << jt->name() << endl;
        }
    }
    
    //Тестовый массив
    int arr[13] = {32,41,23,20,52,67,52,34,2,5,23,52,3};
    vector<int> A (arr,arr+13);
    
    //Создаем класс сортировки
    if (ABaseSort *sort = loader.create("bubbleSort"))
    {
        //Загружаем в него вектор
        sort->loadVector(A);
        
        //Сортируем
        sort->sort();

        //Забираем результат
        auto vect = sort->getArray();

        //Наслаждаемся
        for (auto it = vect.begin(); it != vect.end(); ++it)
            cout << *it << " ";
        cout << endl;

        //Отмечаем объект на автоудаление
        loader.classFor("bubbleSort").autoDelete(sort);
    }
    
    //Далее повторяем тоже самое для stableSort
    if (ABaseSort *sort = loader.create("stableSort"))
    {
        sort->loadVector(A);
        sort->sort();

        auto vect = sort->getArray();

        for (auto it = vect.begin(); it != vect.end(); ++it)
            cout << *it << " ";
        cout << endl;

        loader.classFor("stableSort").autoDelete(sort);
    }
}

Таким образом, мы можем изменять логику работы программы, не перекомпилируя её полностью. Достаточно перекомпилировать отдельные её модули и "скармливать" их программе.

Заключение


Выше приведённые примеры показывают некоторые особенности разработки с использованием библиотеки POCO. Вы можете заметить, что создание приложения или службы на POCO не трудоемкая работа. В дальнейшем хотелось бы рассказать подробно о модулях XML, ZIP, Data, Net. Поподробней остановится на создании высокопроизводительных серверов на POCO. Разобрать систему оповещения и событий (Notifications & Events), систему кэширования и модуль криптографии.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 24.07.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
TeeChart for .NET with source code single license
Traffic Inspector GOLD 5 Учетных записей
Microsoft 365 Apps for business (corporate)
VMware Workstation 14 Pro for Linux and Windows, ESD
Quest Software. Toad for SQL Server Development Suite
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
eManual - электронные книги и техническая документация
Мастерская программиста
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100